#include "src/net/http/http_server.h"

#include <any>

#include "src/base/fio66_logger.h"
#include "src/net/event_loop.h"
#include "src/net/tcp_connection.h"
#include "src/net/http/http_request.h"
#include "src/net/http/http_response.h"
#include "src/net/http/http_context.h"

namespace fio66::net {
namespace detail {
void DefaultHttpCallback(const HttpRequest&, HttpResponse* resp) {
  resp->SetStatusCode(HttpStatusCode::k404NotFound);
  resp->SetStatusMessage("Not Found");
  resp->SetCloseConnection(true);
}
}  // namespace detail

HttpServer::HttpServer(EventLoop* loop, const InetAddress& listen_addr,
                       std::string_view name, TcpServer::Option option)
    : server_(loop, listen_addr, name.data(), option),
      callback_(detail::DefaultHttpCallback) {
  server_.SetConnectionCallback(
      std::bind(&HttpServer::OnConnection, this, std::placeholders::_1));
  server_.SetMessageCallback(
      std::bind(&HttpServer::OnMessage, this, std::placeholders::_1,
                std::placeholders::_2, std::placeholders::_3));
}

EventLoop* HttpServer::GetLoop() const { return server_.GetLoop(); }

void HttpServer::SetHttpCallback(const HttpCallback& cb) { callback_ = cb; }

void HttpServer::SetThreadNum(int num) { server_.SetThreadNum(num); }

void HttpServer::Start() {
  LOGW << "HttpServer[" << server_.Name() << "] Starts listening on "
       << server_.IpPort();
  server_.Start();
}

void HttpServer::OnConnection(const TcpConnectionPtr& conn) {
  if (conn->Connected()) {
    conn->SetContext(HttpContext());
  }
}

void HttpServer::OnMessage(const TcpConnectionPtr& conn, Buffer* buf,
                           Timestamp receive_time) {
  HttpContext* context = std::any_cast<HttpContext>(conn->GetMutableContext());
  if (!context->ParseRequest(buf, receive_time)) {
    conn->Send("HTTP/1.1 400 Bad Request\r\n\r\n");
  }

  if (context->GotAll()) {
    OnRequest(conn, context->Request());
    context->Reset();
  }
}

void HttpServer::OnRequest(const TcpConnectionPtr& conn,
                           const HttpRequest& req) {
  auto connection = req.GetHeader("Connection");
  bool close =
      connection == "close" ||
      (req.Version() == HttpVersion::kHttp10 && connection != "Keep-Alive");
  HttpResponse response(close);
  callback_(req, &response);
  Buffer buf;
  response.AppendToBuffer(&buf);
  conn->Send(&buf);
  if (response.CloseConnection()) {
    conn->Shutdown();
  }
}
}  // namespace fio66::net
